package com.google.code.yargon.ui.lighting;

import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;

import org.apache.log4j.Logger;

import com.google.code.yargon.Yargon;
import com.google.code.yargon.util.LogUtil;
import com.google.code.yargon.util.YargonUtil;

public class RayCast {
	
	private static Logger log = LogUtil.getInstance();
	
	/**
	 * Calculate all required rays for vision radius, assuming players eyes are at 0,0. Which means that the block the player sits at occupies 
	 * coordinates -8..+8 on both axis (with default tile set size 16).<br><br>. Second tile is at 8..24 and so on.
	 * 
	 * Radius is given in unit of blocks 
	 *  
	 * @param radius visibility radius
	 */
	public static Ray[] calculateRays(int radius) {
		
		// Start time
		long st = YargonUtil.getTime();
		
		// Tile size
		int ts = Yargon.getInstance().getCurrentTileSet().getSize();
				
		// Eye, from which all the rays emit
		Point2D p0 = new Point2D.Float(0, 0);	
		
		// Ray target points
		Point2D[] targets = new Point2D.Float[4];
		
		// All the blocks that are intersected by a single ray (a point in block units)
		ArrayList<Intersection> intersections = new ArrayList<Intersection>();
	
		// All the rays, the array is the final storage once it's size is known, ArrayList is used when constructing
		ArrayList<Ray> rays = new ArrayList<Ray>(); 
		Ray[] finalRays = null;
		
		// Visibility radius in points, considering that the block corners at the (block) axis of the player are a bit further than where the most distant face intersects the axis
		double pRadius = p0.distance(new Point2D.Float((ts / 2) + (radius * ts), ts / 2)); 
		
		// Distance to the most distant corner of current block in points 
		double dMax = 0; 
		
		// Loop through positive quadrant of the field of vision. Other quadrants can be translated from this
		for(int y = -1; y < radius; y++) {
			for(int x = -1; x < radius; x++) {
				
				// Ray targets
				targets[0] = new Point2D.Float((ts / 2) + (x * ts),            (ts / 2) + (y * ts));
				targets[1] = new Point2D.Float((ts / 2) + (x * ts) + ts,       (ts / 2) + (y * ts));
				targets[2] = new Point2D.Float((ts / 2) + (x * ts) + ts,       (ts / 2) + (y * ts) + ts);
				targets[3] = new Point2D.Float((ts / 2) + (x * ts),            (ts / 2) + (y * ts) + ts);
								
				// Find the distance to the most distant corner
				for(int t = 0; t < targets.length; t++) {
					double d = p0.distance(targets[t]);
					if(d > dMax) {
						dMax = d;
					}
				}
				
				// If the most distant corner is inside the visibility radius, add the rays for this block
				if(dMax <= pRadius) {
					for(int t = 0; t < targets.length; t++) {
						Ray ray = new Ray(p0, targets[t], x + 1, y + 1);
						rays.add(ray);
							
						// Find all the blocks this ray intersects. There's room for optimization here, no need to loop through all quadrant, but since this is pre-calculated it is not too important now...		
						for(int by = -1; by < radius; by++) {
							for(int bx = -1; bx < radius; bx++) {
								Rectangle2D block = new Rectangle2D.Float((ts / 2) + (bx * ts), (ts / 2) + (by * ts), ts, ts);
								if(ray.intersects(block)) {
									Intersection is = new Intersection(bx + 1, by + 1);
									is.setPoints(Intersection.getIntersections(block, ray));	
									if(x == bx && y == by) {
										is.setOnTarget();
									}
									intersections.add(is);
								}
							}
						}
							
						// Store the intersections
						ray.setIntersections(intersections.toArray(new Intersection[intersections.size()]));
						intersections.clear();
					}
				}
				dMax = 0;
			}	
		}
		
		// Translate first quadrant rays for the rest of the 3 quadrants and put them in the final array. Quadrants go clockwise positive being q1
		finalRays = rays.toArray(new Ray[rays.size() * 4]);
		int i = 0;
		for(Ray q1 : rays) {
			Ray q2 = new Ray(q1.getP1(), new Point2D.Float( (float)q1.getP2().getY(), -(float)q1.getP2().getX()),  q1.getBlockY(), -q1.getBlockX());	// For q2 swap x,y; invert y
			Ray q3 = new Ray(q1.getP1(), new Point2D.Float(-(float)q1.getP2().getX(), -(float)q1.getP2().getY()), -q1.getBlockX(), -q1.getBlockY());	// For q3 invert x,y
			Ray q4 = new Ray(q1.getP1(), new Point2D.Float(-(float)q1.getP2().getY(),  (float)q1.getP2().getX()), -q1.getBlockY(),  q1.getBlockX()); 	// For q4 swap x,y; invert x
	
			// Translate intersected blocks as well
			q2.setIntersections(q1.getIntersections());
			q2.swapIntersectionsXY();
			q2.invertIntersectionsY();
				
			q3.setIntersections(q1.getIntersections());
			q3.invertIntersectionsX();
			q3.invertIntersectionsY();
			
			q4.setIntersections(q1.getIntersections());
			q4.swapIntersectionsXY();
			q4.invertIntersectionsX();
					
			finalRays[i] = q1;
			finalRays[i + 1] = q2;
			finalRays[i + 2] = q3;
			finalRays[i + 3] = q4;
			
			i += 4;
		}
		
		for(Ray ray : finalRays) {
			log.trace("Ray (to block at "+ray.getBlockX()+","+ray.getBlockY()+"): "+ray.getX1()+","+ray.getY1()+" - "+ray.getX2()+","+ray.getY2());
			for(Intersection is : ray.getIntersections()) {
				log.trace("   Intersects block: "+is.getX()+","+is.getY());
			}
		}
		
		log.debug("Calculating visibility radius "+radius+" raycast took "+(YargonUtil.getTime() - st)+" ms");
		return finalRays;
	}
}
